package com.mdl.springmvc.handler;

import com.mdl.springmvc.handler.entity.User;
import com.mdl.springmvc.handler.service.UserSerive;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;

@SessionAttributes(value = {"user"},types = String.class)
@Controller
public class HelloWorld {
	private static Logger logger = Logger.getLogger(HelloWorld.class);
	
	private static final String SUCCESS = "success";
	public HelloWorld(){
		logger.info("HelloWorld Constructor...");
	}
	@Autowired
	private UserSerive userSerive;

	@RequestMapping("/testRedirect")
	public String testRedirect(){
		System.out.println("testRedirect");
		return "redirect:/index.jsp";
	}
	@RequestMapping("/testView")
	public String testView(){
		System.out.println("testView");
		return "helloView";
	}

	@RequestMapping("/testViewAndViewResolver")
	public String testViewAndViewResolver(){
		System.out.println("testViewAndViewResolver");
		return SUCCESS;
	}






	/**
	 * 1.使用@RequestMapping注解来映射请求的URL
	 * 2.返回值会通过视图解析器解析为实际的物理视图，对于InternalResourceViewResolver视图解析器，会做如下的解析：
	 * 通过prefix + returnVal + 后缀 这样的方式得到实际的物理视图，然后做转发操作
	 * @return
	 */
	
	/**
	 * 1.@RequestMapping除了可以修饰方法，还可以修饰类
	 * 
	 * 2.类定义处：提供初始的请求映射信息。相对于WEB应用的根目录
	 * 3.方法处：提供进一步的细分映射信息。相对于类定义处的URL。
	 * 若类定义处未标注@RequestMapping，则方法处标记的URL相对于WEB应用的根目录
	 * 
	 * @return
	 */
	@RequestMapping("/helloworld")
	public String hello() {
		System.out.println("hello springmvc!");
		return SUCCESS;
	}

	/**
	 * 常用：使用method属性来指定请求方式
	 */
	@RequestMapping(value = "/testMethod", method = RequestMethod.POST)
	public String testMethod() {
		System.out.println("testMethod");
		return SUCCESS;
	}

	/**
	 * 了解：可以使用params和headers来精确的映射请求
	 * params和headers支持简单的表达式。
	 * @return
	 */
	@RequestMapping(value = "/testParamsAndHeaders"
			, params = {"username","age!=10"}
//			,headers = {"Accept-Language=en-US,zh;q=0.8"})
//			,headers = {"Accept-Language=en"}
			)
	public String testParamsAndHeaders() {
		System.out.println("testParamsANdHeaders");
		return SUCCESS;
	}

	@RequestMapping("/testAntPath/*/abc")
	public String testAntPath(){
		System.out.println("testAntPath");
		return SUCCESS;
	}

	/**
	 * @PathVariable 可以来映射URL中的占位符到目标方法的参数中
	 * @param id
	 * @return
	 */
	@RequestMapping("/testPathVariable/{id}")
	public String testPathVariable(@PathVariable("id")Integer id){
		System.out.println("testPathVariable:"+id);
		return SUCCESS;
	}

	/**
	 * Rest风格的URL。
	 * 以CRUD为例：
	 * 新增：/order POST
	 * 修改：/order/1 PUT		update?id=1
	 * 获取：/order/1 GET		get?id=1
	 * 删除：/order/1 DELETE		delete?id=1
	 *
	 * 如何发送PUT请求和DELETE请求呢？
	 * 1.需要配置HiddenHttpMethodFilter
	 * 2.需要发送POST请求
	 * 3.需要在发送POST请求是携带一个name="_method"的隐藏域，值为DELETE或PUT
	 *
	 * 在SpringMVC中的目标方法中，如何得到id呢？
	 * 使用@PathVariable注解占位符
	 * @param id
	 * @return
	 */
	@RequestMapping(value = "/testRest/{id}",method = RequestMethod.GET)
	public String testRest(@PathVariable("id")Integer id){
		System.out.println("testRest GET:"+id);
		return SUCCESS;
	}

	@RequestMapping(value = "/testRest",method = RequestMethod.POST)
	public String testRest(){
		System.out.println("testRest POST");
		return SUCCESS;
	}

	@RequestMapping(value = "/testRest/{id}",method = RequestMethod.DELETE)
	public String testRestDelete(@PathVariable("id")Integer id){
		System.out.println("testRest DELETE:"+id);
		return SUCCESS;
	}

	@RequestMapping(value = "/testRest/{id}",method = RequestMethod.PUT)
	public String testRestpUT(@PathVariable("id")Integer id){
		System.out.println("testRest PUT:"+id);
		return SUCCESS;
	}

	/**
	 * @RequestParam来映射请求参数。
	 * value 值即请求参数的参数名
	 * required 该参数是否必须，默认为true
	 * defaultValue 请求参数的默认值
	 * @param username
	 * @param age
	 * @return
	 */
	@RequestMapping(value = "/testRequestParam")
	public String testRequestParam(@RequestParam(value = "username") String username,
								   @RequestParam(value = "age",required = false,defaultValue = "0") Integer age){
		System.out.println("testRequestParam username:" + username + ",age:" + age);
		return SUCCESS;
	}

	/**
	 * 了解：
	 * 映射请求头信息
	 * 用法同@RequestParam
	 * @param al
	 * @return
	 */
	@RequestMapping(value = "/testRequestHandler")
	public String testRequestHandler(@RequestHeader(value = "Accept-Language") String al){
		System.out.println("testRequestHandler Accept-Language:" + al );
		return SUCCESS;
	}

	/**
	 * 了解：
	 * @CookieValue：映射一个Cookie值，属性同@RequestParam
	 * @param sessionId
	 * @return
	 */
	@RequestMapping(value = "/testCookieValue")
	public String testCookieValue(@CookieValue("JSESSIONID")String sessionId){
		System.out.println("testCookieValue sessionId:" + sessionId );
		return SUCCESS;
	}

	/**
	 * Spring MVC会按请求参数名和POJO属性名进行自动匹配，自动为该对象填充属性值。支持级联属性。如：dept.deptId、dept.address.tel等。
	 * @param user
	 * @return
	 */
	@RequestMapping("/testPojo")
	public String testPojo(User user){
		System.out.println("testPojo user:" + user );
		return SUCCESS;
	}

	/**
	 * 可以使用Servlet原生的API作为目标方法的参数
	 * 具体支持以下类型
	 * HttpServletRequest
	 * HttpServletResponse
	 * HttpSession
	 * java.security.Principal
	 * Locale
	 * InputStream
	 * OutputStream
	 * Reader
	 * Writer
	 * @param request
	 * @param response
	 * @return
	 */
	@RequestMapping("/testServletAPI")
	public void testServletAPI(HttpServletRequest request,
							   HttpServletResponse response,
							   Writer out) throws IOException {
		System.out.println("testServletAPI request:" + request + ",response:" + response);
		out.write("hello Springmvc");
//		return SUCCESS;
	}

	/**
	 * 目标方法的返回值可以是ModelAndView类型。
	 * 其中可以包含视图和模型信息
	 * SpringMVC会把ModelAndView中的model数据放到request1域对象中。
	 * @return
	 */
	@RequestMapping("/testModelAndView")
	public ModelAndView testModelAndView(){
		String viewName = SUCCESS;
		ModelAndView modelAndView = new ModelAndView(viewName);
		//添加模型数据到ModelAndView中。
		modelAndView.addObject("time",new Date());
		modelAndView.addObject("name","zhangsan");
		return modelAndView;
	}

	/**
	 * 目标方法可以添加Map类型的参数（实际上也可以是Model类型或ModelMap类型）的参数
	 * @param map
	 * @return
	 */
	@RequestMapping("/testMap")
	public String testMap(Map<String,Object> map){
		System.out.println(map.getClass().getName());
		List<String> names = Arrays.asList("Tom","Jerry","Mike");
		map.put("names", names);
		return SUCCESS;
	}

	/**
	 * @SessionAttributes除了可以通过属性名指定需要放到会话中的属性外(实际上使用的是value属性值)，
	 * 还可以通过模型属性的对象类型指定哪些模型属性需要放到会话中（实际上使用的是types属性值）
	 * 需要注意的是，这个注解只能放在类上面，而不能修饰方法
	 * @param map
	 * @return
	 */
	@RequestMapping("/testSessionAttributes")
	public String testSessionAttributes(Map<String,Object> map){
		User user = new User("Tom","123456","mdl@163.com",15);
		map.put("user",user);
		map.put("school","齐鲁");
		return SUCCESS;
	}

	/**
	 * 运行流程
	 * 1.执行@ModelAttribute修饰的方法：从数据库中取出对象，把对象放在map中，键为user
	 * 2.SpringMVC从Map中获取user对象，并把表单的请求参数赋给该user对象的对应属性。
	 * 3.SpringMVC把上述对象传入目标方法的参数。
	 * 注：在@ModelAttribute修饰的方法中，放入到Map的时候的键需要和目标方法入参类型的第一个字母小写的字符串一致！
	 *
	 * SpringMVC确定目标方法POJO类型入参的过程
	 * 1.确定一个key:
	 * ①若目标方法的POJO类型的参数没有使用@SessionAttributes作为修饰，则key为POJO类名第一个字母的小写
	 * ②若使用了@SessionAttributes来修饰，则key为@SessionAttributes注解的value属性值。
	 * 2.在implicitModel中查找key对应的对象，若存在，则作为入参传入
	 * ①若在@SessionAttributes标记的方法中在Map中保存过，且key和1中确定的key一致，则会获取到。
	 * 3.若implicitModel中不存在key对应的对象，则检查当前的Handler是否使用@SessionAttributes注解修饰，
	 * 若使用了该注解，且@SessionAttributes注解的value属性值中包含了key，则会从HTTPSession中来获取key所对应的
	 * value值，若存在则直接传入到目标分昂发的入参中，若不存在则抛出异常。
	 * 4.若Handler没有标识@SessionAttributes注解或@SessionAttributes注解的value值中不包含key，则会通过反射来创建POJO类型的参数，
	 * 传入目标方法的参数
	 * 5.SpringMVC会把key和POJO类型的对象保存到implicitModel中，进而会保存到request中。
	 *
	 * 源码分析流程：
	 * 1.调用@ModelAttribute注解修饰的方法。实际上把@@ModelAttribute方法中Map中参数放在implicitModel中。
	 * 2.解析请求处理器的目标参数，实际上该目标参数来自于WebDataBinder对象的target属性
	 * ①创建WebDataBinder对象：
	 * --确定objectName属性：若传入的attrName属性值为""，则objectName为类名第一个参数小写。
	 * *注意：attrName，若目标方法的POJO属性使用了@ModelAttribute来修饰，则attrName值即为@ModelAttribute的value属性值
	 * --确定target属性：
	 * >在implicitModel中查找attrName对应的属性值，若存在，ok
	 * >*若不存在：则验证当前Handler是否使用了@SessionAtrributes进行修饰，若使用了，则尝试从Session中
	 * 获取attrName所对应的属性值。若session中没有对应的属性值，则抛出了异常。
	 * >若Handler没有使用@SessionAtrributes进行修饰，或@SessionAtrributes中没有使用value值指定的key
	 * 和attrName相匹配，则通过反射创建了POJO对象
	 * ②SpringMVC把请求的参数赋给了WebDataBinder的target对应的属性
	 * ③*SpringMVC会把WebDataBinder的attrName和target给到implicitModel。进而传到request域对象中。
	 * ④把WebDataBinder的target作为参数传递给目标方法的入参。
	 * @param user
	 * @return
	 */
	@RequestMapping("/testModelAttribute")
	public String testModelAttribute(@ModelAttribute(value = "mn") User user){
			System.out.println("修改：" + user);
		return SUCCESS;
	}

	/**
	 * 1.有@ModelAttribute标记的方法，会在每个目标方法执行之前被SpringMVC调用！
	 * 2.@ModelAttribute注解也可以来修饰目标方法POJO类型的入参，其value属性有如下作用：
	 * ①SpringMVC会使用value属性值在implicitModel中查找对应的对象，若存在则会直接传入到目标方法的入参中
	 * ②SpringMVC会以value为key，POJO类型对象为value，存入到request中。
	 * @param id
	 * @param map
	 */
	@ModelAttribute
	public void getUser(@RequestParam(value = "id",required = false) Integer id,Map<String,Object> map){
		System.out.println("modelAttribute method");
		if(id!=null){
			//模拟从数据库获取对象
			User user = new User(1,"Tom","123456","mdl@163.com",12);
			System.out.println("从数据库获取一个对象："+user);
			map.put("abc",user);
		}
	}


}
